# Copyright (c) 2011 Sergey K.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.


import re

from collections import namedtuple


__all__ = [
    'control',
    'tag',
]


class CacheControl(object):

    def __init__(self,
                 maxage='',
                 smaxage='',
                 private=False,
                 public=False,
                 data=''
                 ):
        self.maxage = maxage
        self.smaxage = smaxage
        self.private = private
        self.public = public
        self.data = data

    def get_values(self, cache_control):
        values = {}
        def set_values(cc):
            if cc:
                for s in cc.split(','):
                    d = s.strip().split('=')
                    v = None if len(d) == 1 else d[1]
                    values.setdefault(d[0], v)
        set_values(cache_control)
        maxage = self.maxage
        if maxage is not None and not isinstance(maxage, basestring):
            maxage = str(maxage)
        smaxage = self.smaxage
        if smaxage is not None and not isinstance(smaxage, basestring):
            smaxage = str(smaxage)
        if maxage:
            values.setdefault('max-age', str(maxage))
        if smaxage:
            values.setdefault('s-maxage', str(smaxage))
        if self.public:
            values.setdefault('public')
        if self.private:
            values.setdefault('private')
        set_values(self.data)
        return values

    def __call__(self, target):
        CacheManager.cache_controls.setdefault(target, self)
        return target

    def set(self, response_headers):
        values = self.get_values(response_headers.get('cache-control'))
        if not values:
            tagged = ('expires' in response_headers or
                      'etag' in response_headers or
                      'last-modified' in response_headers)
            if tagged:
                values.setdefault('private')
                values.setdefault('must-revalidate')
            else:
                values.setdefault('no-cache')
        elif ('private' not in values and
              'public' not in values and
              's-maxage' not in values):
            values.setdefault('private')
        value = ', '.join([k if not v else '{0}={1}'.format(k,v)
                           for (k,v) in values.iteritems()])
        if value:
            response_headers['cache-control'] = value


class Tag(object):

    text_pattern = re.compile(r'[\S ]+')
    etag_pattern = re.compile(r'"(?P<text>[\S ]+)"|W/"(?P<wtext>[\S ]+)"')
    etag_format = '"{0}"'
    
    def __init__(self, etag=None, last_modified=None):
        if etag is not None:
            self.etag = etag
        if last_modified is not None:
            self.last_modified = last_modified

    def get_etag(self, entity):
        etag = (self.etag(entity)
                if callable(self.etag)
                else self.etag)
        if etag is None:
            return
        if not isinstance(etag, basestring):
            etag = Tag.etag_format.format(etag)
        m = Tag.etag_pattern.match(etag)
        if not m:
            text = etag
            etag = Tag.etag_format.format(etag)
        else:
            text = m.group('text') or m.group('wtext')
        if '"' in text or not Tag.text_pattern.match(text):
            raise ValueError('Supplied ETag value has invalid format. '
                             'See RFC 2616, 3.11 Entity Tags '
                             'for valid format.')
        return etag

    def get_last_modified(self, entity):
        from rs.message import format_date
        last_modified = (self.last_modified(entity)
                         if callable(self.last_modified)
                         else self.last_modified)
        if last_modified is None:
            return
        if hasattr(last_modified, 'timetuple'):
            last_modified = format_date(last_modified)
        elif isinstance(last_modified, float):
            last_modified = format_date(last_modified)
        elif not isinstance(last_modified, basestring):
            raise ValueError('Unsupported type of last_modified return value.')
        return last_modified

    def __call__(self, target):
        CacheManager.cache_tags.setdefault(target, self)
        return target

    def set(self, response_headers, entity):
        etag = self.get_etag(entity) if self.etag is not None else None
        last_modified = (self.get_last_modified(entity)
                         if self.last_modified is not None else None)
        if etag:
            response_headers.setdefault('etag', etag)
        if last_modified:
            response_headers.setdefault('last-modified', last_modified)

    def etag(self, entity):
        """
        This method is intend to be implemented in subclasses.
        """

    def last_modified(self, entity):
        """
        This method is intend to be implemented in subclasses.
        """


control = CacheControl
tag = Tag


class CacheManager(set):

    CacheEntry = namedtuple('CacheEntry', 'cache_control, tag')
    EmtpyCacheControl = CacheControl()
    EmptyEntry = CacheEntry(EmtpyCacheControl, None)

    cache_controls = {}
    cache_tags = {}

    def __init__(self, resources=()):
        self.cache = dict((k, CacheManager.CacheEntry(
            self.cache_controls.get(k, CacheManager.EmtpyCacheControl),
            self.cache_tags.get(k))
            ) for k in set(self.cache_controls.keys()+self.cache_tags.keys())
              if k in [r.target for r in resources])
        super(CacheManager, self).__init__(resources)


    def get_cache(self, resource):
        assert resource.target
        return self.cache.get(resource.target, self.EmptyEntry)

